package org.fjsei.yewu.bpm;


import io.camunda.tasklist.dto.Task;
import io.camunda.zeebe.client.api.response.ProcessInstanceEvent;
//import io.camunda.zeebe.spring.client.lifecycle.ZeebeClientLifecycle;
import io.camunda.zeebe.client.ZeebeClient;
import lombok.extern.slf4j.Slf4j;
import md.cm.flow.ApprovalStmRepository;
import md.log.CirculationRepository;
import md.log.Opinion_Enum;
import md.specialEqp.inspect.IspRepository;
import md.system.User;
import org.fjsei.yewu.filter.Uunode;
import org.fjsei.yewu.resolver.Comngrql;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Service;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertTrue;

/**
 * 业务逻辑方法  和 实体类存储类 分离开。
*/
@Slf4j
@ConditionalOnBean(ZeebeClient.class)
@Service
public class AskForApprovalServiceImpl extends Comngrql implements AskForApprovalService {

    @Autowired
    private CirculationRepository circulationRepository;
    @Autowired
    private ApprovalStmRepository approvalStmRepository;
    @Autowired
    private IspRepository iSPRepository;

    //文档来自 https://github.com/camunda-community-hub/spring-zeebe
    private final ZeebeClient zeebeClient;
    private final UserTaskListService userTaskListService;

    @PersistenceContext(unitName = "entityManagerFactorySei")
    private EntityManager emSei;

    public AskForApprovalServiceImpl(ZeebeClient zeebeClient, UserTaskListService userTaskListService) {
        this.zeebeClient = zeebeClient;
        this.userTaskListService = userTaskListService;
    }

    /**发起一个新的审批流程的实例，初始化流程变量。
     *@param bpmn , 当前BPMN: .bpmnProcessId("ReportApproval");
     *@authr: 窗口人员。
     * 流转动作: 这里仅仅登记历史操作就行了，实际由zeebe管控之后的流程运行图。
     * stm:进来之前就得知当前流程节点了。
     * 返回的流程实例ID=reference，但无法从该id获得UserTask的相关信息，用户任务是流程引擎发出的，没做id关联，目前只能通过流程变量来关联，UserTask仅是流程图单一节点操作。
       每个UserTask都要配置的自定义头部：用户任务目标授权用户assignee 或授权用户组s candidateGroups;
     * 若BPMN文件定义如此的：candidateGroups="=[bossg,tomens]" 必须映射这两个用户组。
     * 【规定】用户组名字不要含有","逗号[和]的三个；可以直接给BPMN定义文件设置单一个变量名:assignGroups,拼接授权用户组字符串[bossg,tomens]。
     * boss: 审核人员。
     * zeebe直接挂钩关系数据库的User.username字段；所以同一个流转引擎服务集群实际挂接单一个关系数据库的用户实体表，UserTask依靠.username来定位唯一性。
     * */
    public Long startFlow(String bpmn,String topic,String entId, String uri, List<User> authr){
        User user=checkAuth();
        //有延迟： 这里发起流程之后：大约10秒钟才能在TaskList网页上面看见到的。 todo: boss=审核人 Report里面预设值
        //String userId= user.getUsername();    //Tool.toGlobalId("User",user.getId());
        AskForApprProcessVariables variables=AskForApprProcessVariables.builder().author(user.getUsername())
                    .ent(entId).uri(uri).topic(topic).build();
//                .setReviewer(stm.getReviewer().getUsername()).setApprover(stm.getApprover().getUsername())
//
        //variables.setBossg(boss);
        //variables.setAssignGroups(List.of("审核小组","group3"));     //特殊数组变量  2019-10-02T08:09:40+08:00
        //variables.setExpirationDate("2022-06-27T11:05:00+08:00");
        //variables.setCreationDate("2022-06-27T11:03:00+08:00");  前后都用"2022-06-27T11:03:00"可以
        Instant instant=Instant.now();
        //   variables.setCreationDate(instant.toString());
        //List<String> uids= signMens.stream().map(men->Tool.toGlobalId("User",men.getId())).collect(Collectors.toList());
        List<String> signMens= authr.stream().map(User::getUsername).collect(Collectors.toList());
        variables.setIspMens(signMens);     //窗口协议受理员
//        variables.setSubitem(subitem);     //主报告的
        //variables.setUri(uri);      "/inspect/fCfLZtwOQ_6HPHewrbtGZUlzcA/report/fc25TU-9Tb6RopbXtb4Ws1JlcG9ydA"
        //只能用字符串表达时间变量；     3分钟之后
//        variables.setExpirationDate(instant.plus(3, ChronoUnit.DAYS).toString());

        //【zeebe终端被挂住】前端连续多次点击的，可能导致产生多个的流程实例。@Transactional也无法阻挡zeebe提交。
        ProcessInstanceEvent processInstance= zeebeClient.newCreateInstanceCommand()
                .bpmnProcessId(bpmn)
                .latestVersion()
                .variables(variables)
                .send().join();     //blocking call!

//        //登记状态机日志加一条 + 状态机状态修正同步。
//        Circulation circ=new Circulation();
//        circ.setApply(stm);     //附属于流程对象
//        circ.setCurrent(stm.getSta());
//        //circ.setNext(next);
//        circ.setMemo(memo);
//        circ.setUser(user);
//        circ.setOpinion(opinion);
//        //Set<User> backAuthUsers=new HashSet<>();
//        Set<User>  otherUsers= stm.getAuthr().stream().filter(one->!one.equals(user)).collect(Collectors.toSet());
//        //stm.getAuthr().remove(user);
//        circ.setMoreUser(new ArrayList<>(otherUsers));     //出错了? moreUser[]里面不允许包含User user ？报错 还是因为stm.getAuthr().remove(user)影响的
//        circulationRepository.save(circ);
//        stm.setCur(circ);
//        stm.getHis().add(circ);
//        //stm.setSta(next);     流程下一个flowNode实际在zeebe流程引擎上决定的，这里不需要知晓。
//        stm.setAuthr(authr);
//        //流程引擎给的流程实例id =reference 必须保存DB数据库。
//        stm.setPrId(processInstance.getProcessInstanceKey());       //long
//        stm.setSta(Procedure_Enum.SIGN);      //可以提前预判必然进入会签了；
//        approvalStmRepository.save(stm);

        return   processInstance.getProcessInstanceKey();    //实际流程变量variables.id::前缀都是这个key--xxx;
    }


//    public Iterable<Isp> findAllISPfilter(Long taskId) {
//        emSei.contains(new Task());
//         List<Isp> allPage2 = iSPRepository.findAll();
//        return allPage2;
//    }


    /**在流程实例已经生成后, 各个环节的用户任务的处理。
     * 流程UserTask节点对应的用户流转命令处理：
     * 参数FlowStat ent：每个申请单或报告都会关联一个DB状态记录流转历史。 ent.getStm()代表状态机；
     * 若Set<User> authr没有提供不修改，直接从ent.getStm().authr来缺省配上, 流转给谁。ApprovalStm提供初始化的配置
     * ent参数: 是关联的报告或申请单实体DB模型。
     * uri参数: 可修改给下一个UserTask(后退？)的前端页面链接。
     * days参数：下一个节点超时时间天数。允许修改zeebe流程变量 规范化 超时时间变量。最少1天整数的，普通流程不可能十分紧急严格到几个小时分钟，用户还未能实时通知到。
     * 【要求zeebe】流程周期尽量短暂，太长必然有变：预期混乱！注意：流程已经产生的，遗留的，BPMN变更不会对旧的流程实例有影响的。
     * 顺带指定一个任务负责人：不一定要求zeebe体现运行计算收费子流程，服务端需要任务负责人。
     * 【代码割裂】毛病啊：zeebe强制抽出部分逻辑状态，flowTo功能单一为何附加其它无关参数？@看着不爽。  前端就该敲定的？多个节点共享导致混乱取值的。
     * */
    @Override
    public String flowTo(String userTaskId, Uunode ent, Opinion_Enum opinion, String memo, Integer days, User nextMan, String uri,Boolean calcfee) {
        User user=checkAuth();
        //没法用 zeebeClient.newModifyProcessInstanceCommand() 来提取流程变量
        Task userTask= userTaskListService.getTask(userTaskId);
        assertNotNull(userTask, () -> "UserTask空");
        //完成本环节用户任务  variables 没有修改部分不用get 后set;
        //Map<String, Object> 变量：Collections.singletonMap("approved", true) 多个变量，时间期限，同意，回退理由,转给多人多用户组。
        //每个流程flowNode所考察的 variables 不见得名字一致的？ 统一化的映射zeebe的模型变量名。
        UUID entId= ent.getId();	  //仅仅是状态和历史操作记录，不是真正的流程引擎服务。
        if(null==entId)   return "没申请单实体";
        Map<String, Object>  variables= new HashMap<>();
        //Map<String, Object>  variables= Collections.singletonMap("approved", true);
        //variables.put("result", "true"); 报错啊failed to evaluate expression ' result=false': expected String but found 'ValBoolean(false)'

        //签字阶段实际用这个变量，而不是approved变量的。 .put("result", "true")报错！ NO BACK 可能的设定取值约定为[Y/N/B /S ];
        String lzResult= opinion == Opinion_Enum.NO? "N": opinion == Opinion_Enum.BACK? "B": "Y";
        variables.put("approved", lzResult);     //不能用字符串，流程引擎zeebe会出现解析错误

//        if(StringUtils.hasText(uri))      variables.put("uri", uri);
        //【不通用的】 流程图分叉与否{要不要进入收费计算子流程}：直接耦合这里接口方法的(User nextMan)参数。
        if(null!=nextMan) {
            //不同于正常@JobWorker注解的 AskForApprProcessVariables定义提取变量。底下"who"变量仅仅短暂生存的某一个流程节点中。
            //【局部变量模式】这样设置的变量不在流程实例当中，仅仅存在于当前节点，通过BPMN引擎流程图中定义Input或Ouput的Mapping机制转移出去 拷贝到流程实例全局变量。
            variables.put("who", nextMan.getUsername());      //转交给谁进一步处理的： 不在流程实例初始化能够预先知道的预定义人员。
        }
        if(Boolean.TRUE==calcfee){
            variables.put("approved", "S");    //表示进入子流程： 进收费的分叉。            dispatcher
        }
        variables.put("say", memo);

        //variables.put("approved", Boolean.TRUE); BPMN图上增加approved=result输出，能免去额外输入approved流程变量。直接用result默认变量替代。
   //     AskForApprProcessVariables variables2=new AskForApprProcessVariables().setUri(uri);
        //前后这两个变量变更办法的作用域不一样的。一个是上级主流程的全局性质变量，一个是本节点的局部的变量传递需要赋值机制。
        //【全局变量修改模式】可实现但有缺点：需要获得流程实例ID,还要另外多出命令要发送newSetVariablesCommand。想办法直接在BPMN图上定义变量的输出赋值模式。
        //zeebeClient.newSetVariablesCommand(流程实例ID xL).variables(variables).send().join();
        //这里UserTask处理接口即兴修改的staff和subitem变量默认是不会传递复制给全局变量的，【最佳】办法是必须在BPMN定义输出Output映射来传给主流程全局变量，zeebe就多一层灵活适配性。
        //【差别】改用.variables(AskForApprProcessVariables x)会直接清空替换所有的变量！第一次才能用。 .variables(variables)
        zeebeClient.newCompleteCommand(Long.parseLong(userTaskId)).variables(variables).send().join();

        log.info("申请单流转,ID={}", entId);
        //stm.setSta(Procedure_Enum.SIGN);  //无法在这设置流程状态？不明确含义：ApprovalStm不适合在这里变更！
        return userTaskId;
    }
    /**提取UserTask在当前流程上的 节点ID
     *节点ID 'agreement_reviewer_check' ==> 环节名字 '审核人把关'
     *
     * @return*/
    @Override
    public Task getFlownode(String userTaskId) {
        //没法用 zeebeClient.newModifyProcessInstanceCommand() 来提取流程变量
        Task userTask= userTaskListService.getTask(userTaskId);
        assertNotNull(userTask, () -> "UserTask空");
//        log.info("申请单流转,ID={}", entId); taskDefinitionId
        //stm.setSta(Procedure_Enum.SIGN);  //无法在这设置流程状态？不明确含义：ApprovalStm不适合在这里变更！
        return userTask;      //userTask.getTaskDefinitionId();
    }

    /**通用申请单的最基本的流转
     * 假如是最后一关审批通过&&需要等待监察反馈的，days必须配置，否则流程报错failed to evaluate expression '{,days:days}': no variable found for name 'days'
     * 下一个关口审批人员在流程实例创建时间就确定的，要在流转中间步骤某节点确认下一个角色人员nextMan，必须在流程图配合who参数输出来替换zeebe引擎的全局变量。who倒腾变换赋值给manager变量。
     * 可能需要的隐含参数？假如某个申请单可以1关审批也可能3关审批的：需要预设值一个变量来表示到底是需要几关审批的流程啊，切换关卡的数目也需要回退给申请人来重新提交的：
     * 【问题】 为了确保意义相容性，”审核人“一般表示的是最后一关的审核角色人员；所以角色名定义采用事先指定。3关审批变化成了1关必然导致审批人含义切换。：交叉？似乎bpmn应该分开独立做。
     *  只需要启动流程时刻提供level， 生成当前是3层改成1层的，后退。  退到申请人步骤：旧的清空人员？前端需要重置level吗。
     * */
    @Override
    public String flowRequestTo(String userTaskId, Uunode ent, Opinion_Enum opinion, String memo, Integer days,
                                User nextMan, String uri,String data,Integer desLevel) {
        User user=checkAuth();
        //没法用 zeebeClient.newModifyProcessInstanceCommand() 来提取流程变量
        Task userTask= userTaskListService.getTask(userTaskId);
        assertNotNull(userTask, () -> "UserTask空");
        //完成本环节用户任务  variables 没有修改部分不用get 后set;
        //Map<String, Object> 变量：Collections.singletonMap("approved", true) 多个变量，时间期限，同意，回退理由,转给多人多用户组。
        //每个流程flowNode所考察的 variables 不见得名字一致的？ 统一化的映射zeebe的模型变量名。
        UUID entId= ent.getId();	  //仅仅是状态和历史操作记录，不是真正的流程引擎服务。
        if(null==entId)   return "没申请单实体";
        Map<String, Object>  variables= new HashMap<>();
        //Map<String, Object>  variables= Collections.singletonMap("approved", true);
        //variables.put("result", "true"); 报错啊failed to evaluate expression ' result=false': expected String but found 'ValBoolean(false)'

        //签字阶段实际用这个变量，而不是approved变量的。 .put("result", "true")报错！ NO BACK 可能的设定取值约定为[Y/N/B /S ];
        String lzResult= opinion == Opinion_Enum.NO? "N": opinion == Opinion_Enum.BACK? "B": "Y";
        variables.put("approved", lzResult);     //不能用字符串，流程引擎zeebe会出现解析错误

//        if(StringUtils.hasText(uri))      variables.put("uri", uri);
        //who在本关通过并流转下一步时才有意义!【不通用的】 流程图分叉与否{要不要进入收费计算子流程}：直接耦合这里接口方法的(User nextMan)参数。
        if(null!=nextMan) {
            //不同于正常@JobWorker注解的 AskForApprProcessVariables定义提取变量。底下"who"变量仅仅短暂生存的某一个流程节点中。
            //【局部变量模式】这样设置的变量不在流程实例当中，仅仅存在于当前节点，通过BPMN引擎流程图中定义Input或Ouput的Mapping机制转移出去 拷贝到流程实例全局变量。
            variables.put("who", nextMan.getUsername());      //转交给谁进一步处理的： 不在流程实例初始化能够预先知道的预定义人员。
            if(null!=desLevel) {
                switch (desLevel) {
                    case 3 -> {
                        variables.put("director", null);
                        variables.put("manager", null);
                        variables.put("auditor", nextMan.getUsername());
                    }
                    case 2 -> {
                        variables.put("director", null);
                        variables.put("manager", nextMan.getUsername());
                        variables.put("auditor", null);
                    }
                    case 1 -> {
                        variables.put("director", nextMan.getUsername());
                        variables.put("manager", null);
                        variables.put("auditor", null);
                    }
                }
            }
        }
        variables.put("say", memo);
        variables.put("data", data);
        //正常最后一个审批节点流转的必须配置days,三状态区分# days==0意味着不需要等待等待监察反馈的情况。
        if(null!=days)
            variables.put("days", days);        //BPMN流程图UserTask执行之后还需要输出变量days给下一个节点的 Output Mapping配置必须加上。
        //variables.put("approved", Boolean.TRUE); BPMN图上增加approved=result输出，能免去额外输入approved流程变量。直接用result默认变量替代。
        //     AskForApprProcessVariables variables2=new AskForApprProcessVariables().setUri(uri);
        //前后这两个变量变更办法的作用域不一样的。一个是上级主流程的全局性质变量，一个是本节点的局部的变量传递需要赋值机制。
        //【全局变量修改模式】可实现但有缺点：需要获得流程实例ID,还要另外多出命令要发送newSetVariablesCommand。想办法直接在BPMN图上定义变量的输出赋值模式。
        //zeebeClient.newSetVariablesCommand(流程实例ID xL).variables(variables).send().join();
        //这里UserTask处理接口即兴修改的staff和subitem变量默认是不会传递复制给全局变量的，【最佳】办法是必须在BPMN定义输出Output映射来传给主流程全局变量，zeebe就多一层灵活适配性。
        //【差别】改用.variables(AskForApprProcessVariables x)会直接清空替换所有的变量！第一次才能用。 .variables(variables)
        zeebeClient.newCompleteCommand(Long.parseLong(userTaskId)).variables(variables).send().join();

        log.info("申请单流转,ID={}", entId);
        //stm.setSta(Procedure_Enum.SIGN);  //无法在这设置流程状态？不明确含义：ApprovalStm不适合在这里变更！
        return userTaskId;
    }
    /**发起 新的 通用申请单流程实例
     * level表示toUser的身份 level=”3“最后审批人，”2“主管领导，”1“直接领导; 直接影响流程图123三层还是1层次审批的。
     * 后面陆续流转需要可能要设置其余级别审核人。
     * level变量没必要放进zeebe引擎中了。
     * */
    public Long startRequestFlow(String bpmn,String topic,String entId, String uri, User toUser,Integer level){
        User user=checkAuth();
        RequestProcessVariables variables=RequestProcessVariables.builder().author(user.getUsername())
                .ent(entId).uri(uri).topic(topic).build();
        Instant instant=Instant.now();
        //   variables.setCreationDate(instant.toString());
        //List<String> uids= signMens.stream().map(men->Tool.toGlobalId("User",men.getId())).collect(Collectors.toList());
        String  toMen= toUser.getUsername();
        if(3==level)
            variables.setAuditor(toMen);
        else if(2==level)
            variables.setManager(toMen);
        else        //if("1"==(level))
            variables.setDirector(toMen);

        //【zeebe终端被挂住】前端连续多次点击的，可能导致产生多个的流程实例。@Transactional也无法阻挡zeebe提交的滞后性，多次提交了。
        ProcessInstanceEvent processInstance= zeebeClient.newCreateInstanceCommand()
                .bpmnProcessId(bpmn)
                .latestVersion()
                .variables(variables)
                .send().join();     //blocking call!

        return   processInstance.getProcessInstanceKey();    //实际流程变量variables.id::前缀都是这个key--xxx;
    }

}

//【考虑】BPMN上设置子流程方式？子流程相对独立的没有和主流程过多的交叉的Flow逻辑牵连画线的，状态变量集合取值在子流程自身内部独立的。
